#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "hashmap.h"
#include "list.h"

int mem_loc_hash(void *ptr) {
	return (long)ptr;
}

int mem_loc_compare(void *a, void *b) {
	return (long)a-(long)b;
}

hasher default_hash = mem_loc_hash;
comparer default_compare = mem_loc_compare;

hashmap_t *make_hashmap(int capacity) {
	hashmap_t *hashmap = malloc(sizeof(hashmap_t));
	hashmap->capacity = capacity;
	hashmap->size = 0;
	hashmap->data = malloc(sizeof(list_t) * capacity);
	int i;
	for (i=0; i<capacity; i++)
		hashmap->data[i] = make_list();
	hashmap->hash = default_hash;
	hashmap->compare = default_compare;
	return hashmap;
}

void hashmap_hash(hashmap_t *hashmap, hasher hash) {
	hashmap->hash = hash;
}

void hashmap_compare(hashmap_t *hashmap, comparer compare) {
	hashmap->compare = compare;
}

int hashmap_size(hashmap_t *hashmap) {
	return hashmap->size;
}

int hashmap_capacity(hashmap_t *hashmap) {
	return hashmap->capacity;
}

void *hashmap_set(hashmap_t *hashmap, void *key, void *value) {
	list_t *bucket = hashmap->data[hashmap->hash(key) % hashmap->capacity];

	// run through the list and find out if that key is already in there
	list_iter_t *iter = list_get_iter(bucket);
	while (list_iter_has_next(iter))
		if (!hashmap->compare(((struct hashentry *)list_iter_next(iter))->key, key)) {
			void *oldvalue = ((struct hashentry *)list_iter_cur(iter))->value;
			((struct hashentry *)list_iter_cur(iter))->value = value;
			free(iter);
			return oldvalue;
		}

	// key isn't already in there - create a new hashentry
	struct hashentry *he = malloc(sizeof(struct hashentry));
	he->key = key;
	he->value = value;
	append_list(bucket, he);
	free(iter);
	return NULL;
}

void *hashmap_get(hashmap_t *hashmap, void *key) {
	list_t *bucket = hashmap->data[hashmap->hash(key) % hashmap->capacity];

	// go through the bucket, find a matching key
	list_iter_t *iter = list_get_iter(bucket);
	while (list_iter_has_next(iter))
		if (!hashmap->compare(((struct hashentry *)list_iter_next(iter))->key, key)) { 
			void *value = ((struct hashentry *)list_iter_cur(iter))->value;
			free(iter);
			return value;
		}

	// nothing found
	free(iter);
	return NULL;
}

void destroy_hashmap(hashmap_t *hashmap) {
	free(hashmap);
}


void hashmap_type(hashmap_t *hashmap, enum hashmap_types type) {
	switch (type) {
	case MEM_LOC:
		hashmap_hash(hashmap, mem_loc_hash);
		hashmap_compare(hashmap, mem_loc_compare);
		break;
	case STRING:
		hashmap_hash(hashmap, string_hasher);
		hashmap_compare(hashmap, string_comparer);
		break;
	default:
		ferrf("internal: invalid hashmap type at %s:%d", __FILE__,  __LINE__);
	}
}

int string_hash(void *vptr) {
	unsigned h = 0, i;
	char *ptr = (char *)vptr;
	for (i=0; i<strlen(ptr); i++) {
		h += i*ptr[i];
	}
	return h;
}

int string_compare(void *a, void *b) {
	return strcmp((char *)a, (char *)b);
}

hasher string_hasher = string_hash;
comparer string_comparer = string_compare;

